#ifndef SH_VARIANT_HPP
#define SH_VARIANT_HPP

#include <typeindex>
#include <iostream>

/**
 * C++11实现C++ 17 variant功能，未完成
 */
namespace sh
{
/** 获取最大的整数 */
template<size_t arg, size_t... rest>
struct IntegerMax;

template<size_t arg>
struct IntegerMax<arg>: std::integral_constant<size_t, arg>
{
};

template<size_t arg1, size_t arg2, size_t... rest>
struct IntegerMax<arg1, arg2, rest...>: std::integral_constant<size_t, arg1 >= arg2 ? IntegerMax<arg1, rest...>::value
                                                                                    : IntegerMax<arg2, rest...>::value>
{
};

/** 获取最大的align */
template<typename... Args>
struct MaxAlign: std::integral_constant<int, IntegerMax<std::alignment_of<Args>::value...>::value>
{
};

/** 是否包含某个类型 */
template<typename T, typename... List>
struct Contains;

template<typename T, typename Head, typename... Rest>
struct Contains<T, Head, Rest...>
    : std::conditional<std::is_same<T, Head>::value, std::true_type, Contains<T, Rest... >>::type
{
};

template<typename T>
struct Contains<T>: std::false_type
{
};

template<typename T, typename... List>
struct IndexOf;

template<typename T, typename Head, typename... Rest>
struct IndexOf<T, Head, Rest...>
{
    enum
    {
        value = IndexOf<T, Rest...>::value + 1
    };
};

template<typename T, typename... Rest>
struct IndexOf<T, T, Rest...>
{
    enum
    {
        value = 0
    };
};

template<typename T>
struct IndexOf<T>
{
    enum
    {
        value = -1
    };
};

template<int index, typename... Types>
struct At;

template<int index, typename First, typename... Types>
struct At<index, First, Types...>
{
    using type = typename At<index - 1, Types...>::type;
};

template<typename T, typename... Types>
struct At<0, T, Types...>
{
    using type = T;
};

template<typename... Types>
class Variant
{
    enum
    {
        data_size = IntegerMax<sizeof(Types)...>::value,
        align_size = MaxAlign<Types...>::value
    };
    using data_t = typename std::aligned_storage<data_size, align_size>::type;
public:
    template<int index>
    using IndexType = typename At<index, Types...>::type;

    Variant(void)
        : type_index_(typeid(void))
    {
    }

    ~Variant()
    {
        destroy(type_index_, &data_);
    }

    Variant(Variant<Types...> &&old)
        : type_index_(old.type_index_)
    {
        move(old.type_index_, &old.data_, &data_);
    }

    Variant(const Variant<Types...> &old)
        : type_index_(old.type_index_)
    {
        copy(old.type_index_, &old.data_, &data_);
    }

    Variant &operator=(const Variant &old)
    {
        copy(old.type_index_, &old.data_, &data_);
        type_index_ = old.type_index_;
        return *this;
    }

    Variant &operator=(Variant &&old)
    {
        move(old.type_index_, &old.data_, &data_);
        type_index_ = old.type_index_;
        return *this;
    }

    template<class T,
        class = typename std::enable_if<Contains<typename std::decay<T>::type, Types...>::value>::type>
    Variant(T &&value)
        : type_index_(typeid(void))
    {
        destroy(type_index_, &data_);
        typedef typename std::decay<T>::type U;
        new(&data_) U(std::forward<T>(value));
        type_index_ = std::type_index(typeid(U));
    }

    template<typename T>
    bool is() const
    {
        return (type_index_ == std::type_index(typeid(T)));
    }

    bool Empty() const
    {
        return type_index_ == std::type_index(typeid(void));
    }

    std::type_index type() const
    {
        return type_index_;
    }

    template<typename T>
    typename std::decay<T>::type &get()
    {
        using U = typename std::decay<T>::type;
        if (!is<U>())
        {
            std::cout << typeid(U).name() << " is not defined. "
                      << "current type is " << type_index_.name() << std::endl;
            throw std::bad_cast{};
        }

        return *(U *) (&data_);
    }

    template<typename T>
    int indexOf()
    {
        return IndexOf<T, Types...>::value;
    }

    bool operator==(const Variant &rhs) const
    {
        return type_index_ == rhs.type_index_;
    }

    bool operator<(const Variant &rhs) const
    {
        return type_index_ < rhs.type_index_;
    }

private:
    void destroy(const std::type_index &index, void *buf)
    {
        [](Types &&...)
        {}((destroy0<Types>(index, buf), 0)...);
    }

    template<typename T>
    void destroy0(const std::type_index &id, void *data)
    {
        if (id == std::type_index(typeid(T)))
            reinterpret_cast<T *>(data)->~T();
    }

    void move(const std::type_index &old_t, void *old_v, void *new_v)
    {
        [](Types &&...)
        {}((move0<Types>(old_t, old_v, new_v), 0)...);
    }

    template<typename T>
    void move0(const std::type_index &old_t, void *old_v, void *new_v)
    {
        if (old_t == std::type_index(typeid(T)))
            new(new_v)T(std::move(*reinterpret_cast<T *>(old_v)));
    }

    void copy(const std::type_index &old_t, const void *old_v, void *new_v)
    {
        [](Types &&...)
        {}((copy0<Types>(old_t, old_v, new_v), 0)...);
    }

    template<typename T>
    void copy0(const std::type_index &old_t, const void *old_v, void *new_v)
    {
        if (old_t == std::type_index(typeid(T)))
            new(new_v)T(*reinterpret_cast<const T *>(old_v));
    }

private:
    data_t data_;
    std::type_index type_index_;
};

}


#endif //SH_VARIANT_HPP
